package com.gitee.wsl.markdown.model

import com.gitee.wsl.markdown.MarkwonConfiguration
import com.gitee.wsl.prop.PropContainer
import com.gitee.wsl.prop.PropContainerImpl
import org.intellij.markdown.ast.ASTNode
import org.intellij.markdown.ast.visitors.Visitor
import kotlin.reflect.KClass


interface MarkwonVisitor: Visitor {
    /**
     * @see MarkwonVisitor.Builder.on
     */
    interface NodeVisitor<N : ASTNode> {
        fun visit(visitor: MarkwonVisitor, n: N)
    }

    /**
     * Primary purpose is to control the spacing applied before/after certain blocks, which
     * visitors are created elsewhere
     *
     * @since 4.3.0
     */
    interface BlockHandler {
        fun blockStart(visitor: MarkwonVisitor, ASTNode: ASTNode)
        fun blockEnd(visitor: MarkwonVisitor, ASTNode: ASTNode)
    }

    interface Builder {
        /**
         * @param node        to register
         * @param nodeVisitor [MarkwonVisitor.NodeVisitor] to be used or null to ignore previously registered
         * visitor for this ASTNode
         */
        fun <N : ASTNode> on(node: KClass<N>, nodeVisitor: NodeVisitor<in N>?): Builder
        //fun <N : ASTNode> on(ASTNode: KClass<N>, nodeVisitor: NodeVisitor<N>): Builder

        fun <N : ASTNode> on(node: KClass<N>, nodeVisitor: (visitor: MarkwonVisitor, node: N)->Unit): Builder{
            return  on(node,nodeVisitor=object:NodeVisitor<N>{
                override fun visit(visitor: MarkwonVisitor, n: N) {
                    nodeVisitor(visitor, n)
                }
            })
        }

        /**
         * @param blockHandler to handle block start/end
         * @see io.noties.markwon.base.ext.MarkwonVisitor.BlockHandler
         *
         * @see BlockHandlerDef
         *
         * @since 4.3.0
         */
        fun blockHandler(blockHandler: BlockHandler): Builder

        fun build(configuration: MarkwonConfiguration, renderProps: PropContainer): MarkwonVisitor
    }

    fun configuration(): MarkwonConfiguration
    fun renderProps(): PropContainer

    //fun builder(): SpannableBuilder

    /**
     * Visits all children of supplied ASTNode.
     *
     * @param node to visit
     */
    fun visitChildren(node: ASTNode)

    /**
     * Executes a check if there is further content available.
     *
     * @param ASTNode to check
     * @return boolean indicating if there are more nodes after supplied one
     */
    fun hasNext(node: ASTNode): Boolean

    /**
     * This method **ensures** that further content will start at a new line. If current
     * last character is already a new line, then it won\'t do anything.
     */
    fun ensureNewLine()

    /**
     * This method inserts a new line without any condition checking (unlike [.ensureNewLine]).
     */
    fun forceNewLine()

    /**
     * Helper method to call `builder().length()`
     *
     * @return current length of underlying [SpannableBuilder]
     */
    fun length(): Int

    /**
     * Clears state of visitor (both [RenderProps] and [SpannableBuilder] will be cleared
     */
    fun clear()

    /**
     * Sets `spans` to underlying [SpannableBuilder] from *start*
     * to *[SpannableBuilder.length]*.
     *
     * @param start start position of spans
     * @param spans to apply
     */
    fun setSpans(start: Int, spans: Any)

    /**
     * Helper method to obtain and apply spans for supplied ASTNode. Internally queries [io.noties.markwon.base.SpanFactory]
     * for the ASTNode (via [io.noties.markwon.base.MarkwonSpansFactory.require] thus throwing an exception
     * if there is no [io.noties.markwon.base.SpanFactory] registered for the ASTNode).
     *
     * @param ASTNode  to retrieve [io.noties.markwon.base.SpanFactory] for
     * @param start start position for further [.setSpans] call
     * @see .setSpansForNodeOptional
     */
    fun <N : ASTNode> setSpansForNode(node: N, start: Int)

    /**
     * The same as [.setSpansForNode] but can be used in situations when there is
     * no access to a ASTNode instance (for example in HTML rendering which doesn\'t have markdown Nodes).
     *
     * @see .setSpansForNode
     */
    fun <N : ASTNode> setSpansForNode(node: KClass<N>, start: Int)
    // does not throw if there is no SpanFactory registered for this ASTNode
    /**
     * Helper method to apply spans from a [io.noties.markwon.base.SpanFactory] **if** it\'s registered in
     * [io.noties.markwon.base.MarkwonSpansFactory] instance. Otherwise ignores this call (no spans will be applied).
     * If there is a need to ensure that specified `ASTNode` has a [SpanFactory] registered,
     * then [.setSpansForNode] can be used. [.setSpansForNode] internally
     * uses [io.noties.markwon.base.MarkwonSpansFactory.require]. This method uses [MarkwonSpansFactory.get].
     *
     * @see .setSpansForNode
     */
    fun <N : ASTNode> setSpansForNodeOptional(node: N, start: Int)

    /**
     * The same as [.setSpansForNodeOptional] but can be used in situations when
     * there is no access to a ASTNode instance (for example in HTML rendering).
     *
     * @see .setSpansForNodeOptional
     */
    fun <N : ASTNode> setSpansForNodeOptional(node: KClass<N>, start: Int)

    /**
     * @since 4.3.0
     */
    fun blockStart(node: ASTNode)

    /**
     * @since 4.3.0
     */
    fun blockEnd(node: ASTNode)
}


internal abstract class MarkwonVisitorFactory {
    abstract fun create(): MarkwonVisitor

    companion object {
        fun create(
            builder: MarkwonVisitor.Builder,
            configuration: MarkwonConfiguration
        ): MarkwonVisitorFactory {
            return object : MarkwonVisitorFactory() {
                override fun create(): MarkwonVisitor {
                    return builder.build(configuration, PropContainerImpl())
                }
            }
        }
    }
}

